package tools;

import interfaces.XMLRMIField;
import interfaces.XMLRMIMethod;
import interfaces.XMLRMISerializable;

import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import type.Base64;
import type.IMyDate;

import com.thaiopensource.validate.ValidationDriver;
import com.thaiopensource.validate.rng.CompactSchemaReader;


/**
 * Classe regroupant des manipulations de fichier XML
 * 
 * Contient la méthode toXML générique pour les objets sérialisable XMLRMI
 * 
 * @author Kevin Pozderec, Faycal Abka
 *
 */
public class XMLTool {

	/**
	 * Méthode permettant de vérifier la validité d'un xml par rapport à un relax NG
	 * @param cheminFichierRNG chemin absolu du fichier xml représentant la relax ng à respecter
	 * @param chaine chaine de caractère String contenant un XML
	 * @return true si valide par rapport à la relax ng false sinon
	 * @throws IOException 
	 * @throws SAXException 
	 */
	public static boolean validerRelaxNG (String cheminFichierRNG,String chaine) throws SAXException, IOException{
		
		ValidationDriver vd = new ValidationDriver(CompactSchemaReader.getInstance());		
		InputSource isg = ValidationDriver.fileInputSource(cheminFichierRNG);

		vd.loadSchema(isg);
		
		InputSource isp = new InputSource(new StringReader(chaine));
		if(vd.validate(isp))
				return true;			
		
		return false;		
		
	}
	
	/*Méthode de conversion d'un xml vers un arbre DOM*/
	public static Document XMLtoDOM (String xml){
		
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		InputSource source = new InputSource(new StringReader(xml));
		
		DocumentBuilder db;
		try {
			db = dbf.newDocumentBuilder();
			return db.parse(source);

		} catch (ParserConfigurationException e) {
			System.err.println("Erreur lors de la conversion XML vers DOM pour le methodResponse");
			e.printStackTrace();
		} catch (SAXException e) {
			System.err.println("Erreur lors de la conversion XML vers DOM pour le methodResponse");
			e.printStackTrace();
		} catch (IOException e) {
			System.err.println("Erreur lors de la conversion XML vers DOM pour le methodResponse");
			e.printStackTrace();
		}	
		
		return null;		
	}
	
	/**
	 * Méthode permettant de retranscrire un objet XMLRMISerializable en chaîne de caractère xml
	 * 
	 * 
	 * @param o : objet qui doit être transformé en XML
	 * @param oid : attribut unique d'une instance de classe
	 * @param type : type à indiquer dans le xml
	 * @return chaîne de caractère au format xmlrmi
	 */
	public static String toXML(Object o, int oid,String type){
		StringBuffer sbf = new StringBuffer();
		
		sbf.append("<object oid=\""+oid+"\" type=\""+type+"\"><fields>");
		Class <?> myself = o.getClass();
		Field[] fieldList = myself.getDeclaredFields();
		XMLRMIField xrmi;
		
		/*Gestion des fields*/
		for (Field field : fieldList){
			if( (xrmi=field.getAnnotation(XMLRMIField.class)) != null){
				sbf.append("<field name=\""+ xrmi.serializationName() + "\"><value>");
				try {
					Object valeurField = field.get(o);
					
					if(xrmi.serializationType().equals("object")){
						sbf.append(((XMLRMISerializable)valeurField).toXML());				
					}
					
					if(xrmi.serializationType().equals("int")){
						sbf.append("<" + xrmi.serializationType()+">");
						sbf.append(valeurField);
						sbf.append("</" + xrmi.serializationType()+">");
					}
					if(xrmi.serializationType().equals("boolean")){
						sbf.append("<" + xrmi.serializationType()+">");
						if((Boolean)valeurField)
							sbf.append("1");
						else
							sbf.append("0");
						sbf.append("</" + xrmi.serializationType()+">");
					}
					if(xrmi.serializationType().equals("string")){
						sbf.append("<" + xrmi.serializationType()+">");
						sbf.append((String)valeurField);
						sbf.append("</" + xrmi.serializationType()+">");
					}
					if(xrmi.serializationType().equals("double")){
						sbf.append("<" + xrmi.serializationType()+">");
						sbf.append(valeurField);
						sbf.append("</" + xrmi.serializationType()+">");
					}
					if(xrmi.serializationType().equals("dateTime.iso8601")){
						sbf.append("<" + xrmi.serializationType()+">");
						IMyDate d = (IMyDate)valeurField;
						sbf.append(d.getYear());
						sbf.append(d.getMonth());
						sbf.append(d.getDay());
						sbf.append("T"+d.getHour()+":"+d.getMinute()+":"+d.getSecond());
						sbf.append("</" + xrmi.serializationType()+">");
					}
					if(xrmi.serializationType().equals("base64")){
						sbf.append("<" + xrmi.serializationType()+">");
						sbf.append(((Base64)valeurField).getChaine64());
						sbf.append("</" + xrmi.serializationType()+">");
					}					
					if(xrmi.serializationType().equals("array")){
						/**EN COURS : Evolution possible ressortir une méthode toXMLArray pour faire une récursion dessus en cas
						 * de array dans un array*/
						
						sbf.append("<" + xrmi.serializationType()+">");
						sbf.append("<data>");
						
						if(o.getClass().isArray()){
							System.err.println("Erreur : Vous tentez de sérialiser en tableau un objet qui n'est pas un tableau");
							return null;
						}
						
						Class <?> typeElement = valeurField.getClass().getComponentType();
						
						/*On considère que seul les types primitif sont utilisée comme élément, par les wrapper (ex: Integer)*/
						if ( typeElement.getName().equals("int")){
							int [] tab = (int[]) valeurField;
							for(int i=0;i<tab.length;i++){
								sbf.append("<value><int>"+tab[i]+"</int></value>");
							}
						}
						if ( typeElement.getName().equals("double")){
							double [] tab = (double[]) valeurField;
							for(int i=0;i<tab.length;i++){
								sbf.append("<value><double>"+tab[i]+"</double></value>");
							}
						}
						if ( typeElement.getName().equals("boolean")){
							boolean [] tab = (boolean[]) valeurField;
							for(int i=0;i<tab.length;i++){
								sbf.append("<value><boolean>");
								if ( tab[i] )
									sbf.append("1");
								else
									sbf.append("0");
								sbf.append("</boolean></value>");
							}
						}
						if ( typeElement.getName().equals("String")){
							String [] tab = (String[]) valeurField;
							for(int i=0;i<tab.length;i++){
								sbf.append("<value><string>"+tab[i]+"</string></value>");
							}
						}
						if ( typeElement.getName().equals("type.IMyDate") || typeElement.getName().equals("type.MyDate")
								|| typeElement.getName().equals("IMyDate") || typeElement.getName().equals("MyDate")) {
							IMyDate [] tab = (IMyDate[]) valeurField;
							for(int i=0;i<tab.length;i++){
								sbf.append("<value><dateTime.iso8601>");
								
								IMyDate d = tab[i];
								sbf.append(d.getYear());
								sbf.append(d.getMonth());
								sbf.append(d.getDay());
								sbf.append("T"+d.getHour()+":"+d.getMinute()+":"+d.getSecond());
								
								sbf.append("</dateTime.iso8601></value>");
							}
						}
						if ( typeElement.getName().equals("type.Base64") || typeElement.getName().equals("Base64")){
							Base64 [] tab = (Base64[]) valeurField;
							for(int i=0;i<tab.length;i++){
								sbf.append("<value><base64>"+tab[i].getChaine64()+"</base64></value>");
							}
						}					
						
						sbf.append("</data>");
						sbf.append("</" + xrmi.serializationType()+">");

					}
					if(xrmi.serializationType().equals("struct")){
						/*Non géré*/
					}					
					
					sbf.append("</value></field>");					
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}				
			}
		}		
		sbf.append("</fields>");
		/*Fin de gestion des fields*/
		
		/*Gestion des méthodes*/
		Method [] methodList = myself.getDeclaredMethods();
		XMLRMIMethod mcast;
		
		sbf.append("<methods>");
		
		for(Method m : methodList){
			if(m.isAnnotationPresent(XMLRMIMethod.class)){
				mcast = (XMLRMIMethod) m.getAnnotation(XMLRMIMethod.class);
				sbf.append("<method language=\""+mcast.language()+"\">"+mcast.code()+"</method>");
			}
		}
		
		sbf.append("</methods></object>");
		/*Fin de gestion des méthodes*/
		
		return sbf.toString();
	}
	
}
